import { LINE_BREAKS, LINE_COLOR, ROUND_RADII } from "./config";
import { Data } from "./data";
import { IPosition, ICanvasOption, IDrawRect, IFontHeight, TCursor } from "./type";
import { gFont } from "./utils";


export abstract class CanvasBase extends Data {
    private _canvas: HTMLCanvasElement;
    private _ctx: CanvasRenderingContext2D;

    private _fontHeightMap: Map<string, IFontHeight> = new Map(); // 字体高度信息 测试文字为换行

    constructor(root: HTMLElement, width: number, height: number) {
        super();
        this._canvas = document.createElement('canvas');
        this._ctx = this._canvas.getContext('2d') as CanvasRenderingContext2D;
        this.setWidthHeight(width, height);
        root.appendChild(this._canvas);
    }
    protected drawRect(x: number, y: number, w: number, h: number, option?: ICanvasOption) {
        this._ctx.beginPath();
        this._ctx.save();
        this._ctx.rect(x, y, w, h);
        this.setCtxType(option);
        if (option?.strokeStyle) {
            this._ctx.stroke();
        } else {
            this._ctx.fill();
        }
        this._ctx.restore();
        this._ctx.closePath();
    }
    protected drawRects(rects: IDrawRect[]) {
        for (const v of rects) {
            const { x, y, w, h, option } = v;
            this.drawRect(x, y, w, h, option);
        }
    }
    protected drawRoundRect(x: number, y: number, w: number, h: number, option?: ICanvasOption) {
        this._ctx.beginPath();
        this._ctx.save();
        this._ctx.roundRect(x, y, w, h, ROUND_RADII);
        this.setCtxType(option);
        if (option?.strokeStyle) {
            this._ctx.stroke();
        } else {
            this._ctx.fill();
        }
        this._ctx.restore();
        this._ctx.closePath();
    }
    protected drawLine(posArray: IPosition[], option?: ICanvasOption) {
        const length = posArray.length;
        if (length < 2) { return; }
        this._ctx.beginPath();
        this._ctx.save();
        this._ctx.lineWidth = 2;
        this._ctx.strokeStyle = LINE_COLOR;
        this._ctx.moveTo(posArray[0].x, posArray[0].y);
        for (let i = 1; i < length; i++) {
            this._ctx.lineTo(posArray[i].x, posArray[i].y);
        }
        this.setCtxType(option);
        this._ctx.stroke();
        this._ctx.restore();
        this._ctx.closePath();
    }
    protected drawText(textStr: string, x: number, y: number, option?: ICanvasOption) {
        this._ctx.beginPath();
        this._ctx.save();
        this._ctx.font = gFont();
        this.setCtxType(option);
        this._ctx.fillText(textStr, Math.floor(x), Math.floor(y));
        this._ctx.restore();
        this._ctx.closePath();
    }
    protected drawArc(x: number, y: number, r: number, startAngle: number, endAngle: number, option?: ICanvasOption) {
        this._ctx.beginPath();
        this._ctx.save();
        this.setCtxType(option);
        this._ctx.arc(x, y, r, startAngle, endAngle);
        this._ctx.stroke();
        this._ctx.restore();
        this._ctx.closePath();
    }
    private setCtxType(option?: ICanvasOption) {
        if (!option) { return; }
        const {
            fillStyle,
            font,
            lineWidth,
            strokeStyle,
            lineCap,
            lineJoin,
        } = option;
        if (fillStyle) {
            this._ctx.fillStyle = fillStyle;
        }
        if (font) {
            this._ctx.font = font;
        }
        if (lineWidth) {
            this._ctx.lineWidth = lineWidth;
        }
        if (strokeStyle) {
            this._ctx.strokeStyle = strokeStyle;
        }
        if (lineCap) {
            this._ctx.lineCap = lineCap;
        }
        if (lineJoin) {
            this._ctx.lineJoin = lineJoin;
        } else {
            this._ctx.lineJoin = 'round'; // 默认圆角
        }
    }
    protected clear() {
        this._ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
    }
    protected addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLCanvasElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void {
        this._canvas.addEventListener(type, listener, options);
    }
    /**
     * 测量文本的宽度和高度
     * 
     * 文字位置会落在 hAscent。hDescent是文字的下边距高度。两个作为整体高度
     * 
     * @param text 
     * @param font 
     * @returns hAscent: 上边距高度, hDescent: 下边距高度, width: 文本宽度
     */
    protected measureSize(text: string, font?: string): { width: number, hAscent: number, hDescent: number } {
        font = font || gFont();
        this._ctx.beginPath();
        this._ctx.save();
        this._ctx.font = font;
        const measureTextInfo = this._ctx.measureText(text);
        this._ctx.restore();
        this._ctx.closePath();

        const { width, fontBoundingBoxAscent, fontBoundingBoxDescent } = measureTextInfo;
        return {
            width,
            hAscent: fontBoundingBoxAscent,
            hDescent: fontBoundingBoxDescent,
        }
    }
    protected setWidthHeight(width: number, height: number) {
        this.canvasWidth = width;
        this.canvasHeight = height;
        const dpr = (window.devicePixelRatio || 1) * 5;
        this._canvas.width = width * dpr;
        this._canvas.height = height * dpr;
        this._canvas.style.width = width + 'px';
        this._canvas.style.height = height + 'px';
        this._ctx.scale(dpr, dpr);
    }
    protected get width() {
        return this.canvasWidth;
    }
    protected get height() {
        return this.canvasHeight;
    }
    // 获取字体高度
    protected getHeightByFont(font: string): IFontHeight {
        const result = this._fontHeightMap.get(font);
        if (result) {
            return result;
        }
        const { hAscent, hDescent } = this.measureSize(LINE_BREAKS, font);
        const r = {
            h: hAscent + hDescent,
            hAscent: hAscent,
            hDescent: hDescent,
        };
        this._fontHeightMap.set(font, r);
        return r;
    }
    // 设置鼠标样式
    protected setCursor(cursor: TCursor) {
        this._canvas.style.cursor = cursor;
    }
}